<?php
/**
 *  Configurable properties of Yz_Property_*:
 *
 *  - sourceName: the name of the source, one of those used by the model
 *
 */
abstract class Yz_Property_Abstract
    extends Yz_Object
    implements Yz_Property_Interface
{

    /**
     *  @var bool
     */
    protected $allowNull;

    /**
     *  @var bool
     */
    protected $readonly;

    /**
     *  @var mixed
     */
    protected $defaultValue;

    /***************************
     *      Public methods     *
     ***************************/

    public function __toString()
    {
        debug();
        return $this->_name_();
    }


    /**
     *  @param array|null config
     *  @param varargs
     *  NOTE use custom constructor for each property class; @see init()
     */
    // public function __construct($config=null)

    /** Test if a value is suitable for the property, possibly adjust
     *  @param mixed value
     *  @return the adjusted value
     *  @throw Yz_Property_Exception_NullValueNotAllowed
     *  @throw InvalidArgument
     */
    public function testValue($value)
    {
        if (false and $this->readonly())
            throw Yz_Property_Exception_ReadonlyProperty::_new();
        return $this->checkNull($value);
    }

    /** Select a default value for an item, based on the property name
     *  @param string $propName
     *  @param Yz_Item_Interface $item
     *  @return default value
     *  @throw Yz_Property_Exception
     */
    public function selectValue($propName, Yz_Item_Interface $item)
    {
        #if (!$this->allowNull())
        #    throw Yz_Property_Exception_NullValueNotAllowed::_new();
        return $this->defaultValue();
    }


    /** Assign a value for an item from an array of values, with prefix
     *  @param string $propName
     *  @param array-type $values
     *  @param string $prefix
     *  @return value
     *  @throw Yz_Property_Exception
     */
    public function assignValue($value)
    {
        return $value;
    }


    /** init config from arguments to __construct
     *  @param array $config which is (expected to be) func_get_args() of __construct
     */
    protected function init(array $config)
    {
        // use optional config
        if ($config) {
            // if first arg is array, use it as config, but also check other args
            if (is_array($config[0])) {
                $rc = $config[0];
                unset($config[0]);
            } else {
                $rc = array();
            }
            // if there are other args, each one is treated as a TRUE value for the key=arg
            if ($config)
                foreach ($config as $opt)
                    $rc[strtolower($opt)] = true;
            $this->__dict__ = $rc;
        }
        return $this;
    }

    /**
     *
     */
    protected function fetchConfig($name, $default=null)
    {
        if (isset($this->__dict__[$name]))
            return $this->__dict__[$name];
        return $default;
    }


    /** Return the value unless it is null and null is not allowed,
     *      in which case throw exception
     *  @param mixed $value
     *  @throw Yz_Property_Exception_NullValueNotAllowed
     *  @return mixed
     */
    protected function checkNull($value)
    {
        if (null === $value && ! $this->allowNull())
            throw Yz_Property_Exception_NullValueNotAllowed::_new();
        return $value;
    }


    /** Whether a null value is allowed
     *  look up a config value `notNull', default to FALSE, which will be inverted
     *  @return bool
     */
    public function allowNull()
    {
        $r =& $this->allowNull;
        if (null === $r) {
            $r = Yz_String::isTrue($this->fetchConfig('notnull', 'no'));
            if (null === $r)
                throw Yz_Property_Exception_InvalidConfig::_new();
            $r = !$r;
        }
        return $r;
    }


    /** Whether a value can be changed
     *  look up a config value `readonly', default to FALSE
     *  @return bool
     */
    public function readonly()
    {
        $r =& $this->readonly;
        if (null === $r) {
            $r = Yz_String::isTrue($this->fetchConfig('readonly', 'no'));
            if (null === $r)
                throw Yz_Property_Exception_InvalidConfig::_new();
        }
        return $r;
    }



    /** Whether a value can be changed
     *  look up a config value `readonly', default to FALSE
     *  @return bool
     */
    public function defaultValue()
    {
        $r =& $this->defaultValue;
        if (null === $r) {
            $r = $this->fetchConfig('defaultvalue', null);
        }
        return $r;
    }

}
